package network;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.math.BigInteger;
import java.util.ArrayList;

import org.hibernate.Session;
import org.hibernate.Transaction;
import org.hibernate.criterion.Restrictions;
import bdd.standalone.hibernate.HibernateUtil;
import bdd.standalone.bean.SiteServer;

import security.diffie.hellmann.CipherModule;
import security.diffie.hellmann.DiffieHellman;
import tools.ConfParser;
import tools.ExecuteExternalApplication;
import tools.FileUtils;
import tools.KeyParser;

/**
 * Traite les commandes reçues par le serveur.
 * @author Guillaume Poças & Jean-François Augé
 */
public class KrakenCommandInterpreter {
	
	private KrakenThread krakenThread;
	private String savedMessage;
	
	private DiffieHellman dh;
	
	/**
	 * Constructeur.
	 * @param thread
	 */
	public KrakenCommandInterpreter(KrakenThread thread){
		this.krakenThread = thread;
		this.savedMessage = "";
	}
	
	/**
	 * Interprete la commande.
	 * @param msg
	 */
	public void interpret(String msg){
		String[] msgCmd;
		@SuppressWarnings("unused")
		String[] testSavedMessage;
		
		System.out.println("KrakenCommandInterpreter: J'analyse : \""+msg+"\"");
		
		msgCmd = msg.split("  ");
		
		//Identification Client-Serveur
		if(msgCmd[0].equalsIgnoreCase(KrakenClientMsg.KRAKEN_CLIENT.toString())){
			krakenThread.sendCommand(KrakenServerMsg.KRAKEN_SERVER.makeCommand());
		}
		
		//nettoyage de répertoire
		else if(msgCmd[0].equalsIgnoreCase(KrakenClientMsg.CLEAN_FOLDER.toString())){
			if(msgCmd.length == 2){
				FileUtils.cleanDirectory(msgCmd[1]);
				krakenThread.sendCommand(KrakenServerMsg.FOLDER_CLEAN.makeCommand());
			}
		}
		
		//copie de fichier
		else if(msgCmd[0].equalsIgnoreCase(KrakenClientMsg.COPY_FILE.toString())){
			if(msgCmd.length == 3){
				this.savedMessage = msgCmd[0];
				krakenThread.sendCommand(KrakenServerMsg.WAITING_FILE.makeCommand(" "+msgCmd[1]," "+msgCmd[2]));
				FileUtils.tcpWaitingFile(krakenThread.getSocket(),msgCmd[2]);
				krakenThread.sendCommand(KrakenServerMsg.END_WAITING_FILE.makeCommand());
			}
		}
		
		//fichier envoyé
		else if(msgCmd[0].equalsIgnoreCase(KrakenClientMsg.FILE_SENDED.toString())){
			if(savedMessage.equalsIgnoreCase(KrakenClientMsg.COPY_FILE.toString())){
				this.savedMessage = "";
				krakenThread.sendCommand(KrakenServerMsg.FILE_SAVED.makeCommand());
			}
		}
				
		//Initialisation du système de chiffrement modulaire
		else if(msgCmd[0].equalsIgnoreCase(KrakenClientMsg.ENCRYPTION_DISTRIBUTION.toString())){
			if(msgCmd.length == 3){				
				if(msgCmd[1].equalsIgnoreCase("GLOBAL")){
					//récupérer chemin où copier sur serv global dans fichier config
					ConfParser parser = new ConfParser();
					try {
						parser.parse(new File("/etc/kraken/global_conf/kraken.conf"));
					} catch (IOException e) {
						e.printStackTrace();
					}
					
					String path = parser.getValue("PATH_ENCRYPTION_DISTRIBUTION");
					krakenThread.sendCommand(KrakenServerMsg.PATH_ENCRYPTION_DISTRIBUTION.makeCommand(" GLOBAL"," "+msgCmd[2]," "+path));
				}
				else if(msgCmd[1].equalsIgnoreCase("LOCAL")){
					//récupérer chemin où copier sur serv local dans fichier config
					ConfParser parser = new ConfParser();
					try {
						parser.parse(new File("/etc/kraken/local_conf/kraken.conf"));
					} catch (IOException e) {
						e.printStackTrace();
					}
					
					String path = parser.getValue("PATH_ENCRYPTION_DISTRIBUTION");
					krakenThread.sendCommand(KrakenServerMsg.PATH_ENCRYPTION_DISTRIBUTION.makeCommand(" LOCAL", " "+msgCmd[2], " "+path));
				}
			}
			else if(msgCmd.length == 4){
				if(msgCmd[1].equalsIgnoreCase("LOCAL")){
					//1 = LOCAL & 2 = ip local & 3 = chemin stockage des fichiers copiés sur le global
					//créer un client avec les messages reçus
					
					String answer = "";
					String ip = msgCmd[2];
					ArrayList<String> msg_to_send = new ArrayList<String>();
					msg_to_send.add(KrakenClientMsg.KRAKEN_CLIENT.toString());
					msg_to_send.add(KrakenClientMsg.ENCRYPTION_DISTRIBUTION.makeCommand(" LOCAL"," "+msgCmd[3]));
					msg_to_send.add(KrakenClientMsg.END_CLIENT.toString());
							
					KrakenClient krakenClient = new KrakenClient(msg_to_send,ip);
					krakenClient.connect();
					krakenClient.communicate();
							
					answer = krakenClient.getFinalAnswer();
					if(answer.equalsIgnoreCase("END_SERVER")){
						System.out.println("Distribution locale des classes réussie");
						krakenThread.sendCommand(KrakenServerMsg.END_ENCRYPTION_DISTRIBUTION.makeCommand());
					}
					else{
						System.out.println("Distribution locale des classes failed");
						krakenThread.sendCommand(KrakenServerMsg.ERROR.makeCommand( " "+KrakenServerMsg.END_ENCRYPTION_DISTRIBUTION.msg));
					}
				}
			}
		}
		
		//Paramétrage du système de chiffrement modulaire
		else if(msgCmd[0].equalsIgnoreCase(KrakenClientMsg.ENCRYPTION_PARAMETER.toString())){
			if(msgCmd.length == 4){
				//1 = nom fichier & 2 = ip global & 3 = ip local
				String answer = "";
				String ip = msgCmd[2];
				ArrayList<String> msg_to_send = new ArrayList<String>();
				msg_to_send.add(KrakenClientMsg.KRAKEN_CLIENT.toString());
				msg_to_send.add(KrakenClientMsg.ENCRYPTION_PARAMETER.makeCommand(" "+msgCmd[1]," "+msgCmd[3]));
				msg_to_send.add(KrakenClientMsg.END_CLIENT.toString());
						
				KrakenClient krakenClient = new KrakenClient(msg_to_send,ip);
				krakenClient.connect();
				krakenClient.communicate();
						
				answer = krakenClient.getFinalAnswer();
				if(answer.equalsIgnoreCase("END_SERVER")){
					System.out.println("Paramétrage local de "+msgCmd[1]+" réussi");
					krakenThread.sendCommand(KrakenServerMsg.END_ENCRYPTION_PARAMETER.makeCommand());
				}
				else{
					System.out.println("Paramétrage local de "+msgCmd[1]+" failed");
					krakenThread.sendCommand(KrakenServerMsg.ERROR.makeCommand( " "+KrakenServerMsg.END_ENCRYPTION_DISTRIBUTION.msg));
				}
				//krakenThread.sendCommand(KrakenServerMsg.END_ENCRYPTION_PARAMETER.makeCommand());
			}
			else if(msgCmd.length == 3){
				//1 = nom fichier & 2 = ip local
				String answer = "";
				String ip = msgCmd[2];
				ArrayList<String> msg_to_send = new ArrayList<String>();
				msg_to_send.add(KrakenClientMsg.KRAKEN_CLIENT.toString());
				msg_to_send.add(KrakenClientMsg.ENCRYPTION_PARAMETER.makeCommand(" "+msgCmd[1]));
				msg_to_send.add(KrakenClientMsg.END_CLIENT.toString());
						
				KrakenClient krakenClient = new KrakenClient(msg_to_send,ip);
				krakenClient.connect();
				krakenClient.communicate();
						
				answer = krakenClient.getFinalAnswer();
				if(answer.equalsIgnoreCase("END_SERVER")){
					System.out.println("Paramétrage local de "+msgCmd[1]+" réussi");
					krakenThread.sendCommand(KrakenServerMsg.END_ENCRYPTION_PARAMETER.makeCommand());
				}
				else{
					System.out.println("Paramétrage local de "+msgCmd[1]+" failed");
					krakenThread.sendCommand(KrakenServerMsg.ERROR.makeCommand( " "+KrakenServerMsg.END_ENCRYPTION_DISTRIBUTION.msg));
				}
				//krakenThread.sendCommand(KrakenServerMsg.END_ENCRYPTION_PARAMETER.makeCommand());
			}
			else if(msgCmd.length == 2){
				//1 = nom fichier
				//copier le fichier directement
				ConfParser parser = new ConfParser();
				try {
					parser.parse(new File("/etc/kraken/local_conf/kraken.conf"));
				} catch (IOException e) {
					e.printStackTrace();
				}
				
				String pathToCopy = parser.getValue("PATH_ENCRYPTION_PARAMETER");
				String pathOfClasses = parser.getValue("PATH_ENCRYPTION_DISTRIBUTION");
				File[] files = FileUtils.listFiles(pathOfClasses);
				String tmp[];
				System.out.println(pathOfClasses);
				
				for(File file : files){
					System.out.println(file.getName());
					tmp = file.getName().split("\\.");
					if(tmp[0].equalsIgnoreCase(msgCmd[1])){
						try {
							FileUtils.cleanDirectory(pathToCopy);
							FileUtils.copyFileBuffered(file.getAbsolutePath(), pathToCopy+file.getName(), 4);
							krakenThread.sendCommand(KrakenServerMsg.END_ENCRYPTION_PARAMETER.makeCommand());
						} catch (FileNotFoundException e) {
							e.printStackTrace();
						} catch (IOException e) {
							e.printStackTrace();
						}
					}
				}
				//krakenThread.sendCommand(KrakenServerMsg.PATH_ENCRYPTION_PARAMETER.makeCommand(" "+msgCmd[1]," "+pathOfClasses, " "+pathToCopy));
			}
		}
		
		//generation clé RSA
		else if(msgCmd[0].equalsIgnoreCase(KrakenClientMsg.GENERATE_RSA_KEY.toString())){
			//appeler le programme pour génerer clé RSA
			krakenThread.sendCommand(KrakenServerMsg.RSA_KEY_GENERATED.makeCommand());
		}
		
		//generation clé
		else if(msgCmd[0].equalsIgnoreCase(KrakenClientMsg.GENERATE_KEY.toString())){
			if(msgCmd.length == 5){
				//servWeb: 1 = algo & 2 = ip global & 3 = ip local & 4 = global name
				String answer = "";
				String ip = msgCmd[2];
				ArrayList<String> msg_to_send = new ArrayList<String>();
				msg_to_send.add(KrakenClientMsg.KRAKEN_CLIENT.toString());
				msg_to_send.add(KrakenClientMsg.GENERATE_KEY.makeCommand(" "+msgCmd[1], " "+msgCmd[3]));
				msg_to_send.add(KrakenClientMsg.END_CLIENT.toString());
						
				KrakenClient krakenClient = new KrakenClient(msg_to_send,ip);
				krakenClient.connect();
				krakenClient.communicate();
						
				answer = krakenClient.getFinalAnswer();
				if(answer.equalsIgnoreCase(KrakenServerMsg.END_SERVER.toString())){
					System.out.println("Génération de clé "+msgCmd[1]+" réussi");
					krakenThread.sendCommand(KrakenServerMsg.KEY_GENERATED.makeCommand());
				}
				else{
					System.out.println("Génération de clé "+msgCmd[1]+" failed");
					krakenThread.sendCommand(KrakenServerMsg.ERROR.makeCommand( " "+KrakenServerMsg.END_ENCRYPTION_DISTRIBUTION.msg));
				}
			}
			else if(msgCmd.length == 3){
				//serv global: 1 = algo & 2 = ip local
				String algo = msgCmd[1];
				String ip = msgCmd[2];
				int out = -10;
				String size;
				
				ConfParser parser = new ConfParser();
				try {
					parser.parse(new File("/etc/kraken/global_conf/kraken.conf"));
				} catch (IOException e) {
					e.printStackTrace();
				}
				String keyPath = parser.getValue("ENCRYPTION_KEY");
				String path = keyPath+algo+".key";
				
				if(algo.equalsIgnoreCase("lfsr"))	size = "31";
				else	size = "64";
				
				FileUtils.cleanDirectory(keyPath);
				String[] args = {parser.getValue("CRYPTO_CIPHER"), "-k", algo, path, size}; 
				
				//prog externe
				try {
					ExecuteExternalApplication eea = new ExecuteExternalApplication();
					out = eea.execute(args);
				} catch (IOException e) {
					e.printStackTrace();
				}
				
				if(out == 0){
					String key = KeyParser.parseKey(algo, path);
					if(key == null){
						System.out.println("Mauvais format de clé");
						//TODO ERR
					}
					else{
						Session session = HibernateUtil.getSession();
						Transaction tx = session.beginTransaction();
						SiteServer site = (SiteServer) session.createCriteria(SiteServer.class).
								add(Restrictions.like("siteserv_ip", ip)).list().get(0);
						site.setSiteserv_key(key);
						session.save(site);
						tx.commit();
						session.close();
						
						//envoie au serv local
						try {
							String dh_key = CipherModule.readDHKey(parser.getValue("DH_KEY"));
							if(!dh_key.equalsIgnoreCase("")){
								String encryptedKeyPath = keyPath+algo+".key_c";
								FileUtils.copyFileBufferedEncryptedDH(keyPath+algo+".key", encryptedKeyPath, 4, dh_key);
								
								//(le serv local dechiffre le fichier arrive)
								String answer = "";
								ArrayList<String> msg_to_send = new ArrayList<String>();
								msg_to_send.add(KrakenClientMsg.KRAKEN_CLIENT.makeCommand());
								msg_to_send.add(KrakenClientMsg.GENERATE_KEY.makeCommand(" "+encryptedKeyPath));
								msg_to_send.add(KrakenClientMsg.END_CLIENT.makeCommand());
								
								KrakenClient krakenClient = new KrakenClient(msg_to_send,ip);
								krakenClient.connect();
								krakenClient.communicate();
								
								//on supprime le fichier cle genere
								FileUtils.cleanDirectory(keyPath);
										
								answer = krakenClient.getFinalAnswer();
								if(answer.equalsIgnoreCase(KrakenServerMsg.END_SERVER.toString())){
									System.out.println("Génération de clé "+msgCmd[1]+" réussi");
									krakenThread.sendCommand(KrakenServerMsg.KEY_GENERATED.makeCommand());
								}
								else{
									System.out.println("Génération de clé "+msgCmd[1]+" failed");
									krakenThread.sendCommand(KrakenServerMsg.ERROR.makeCommand( " "+KrakenServerMsg.END_ENCRYPTION_DISTRIBUTION.msg));
								}
							}
							else	System.out.println("Erreur sur la clé DH");
						} catch (IOException e) {
							e.printStackTrace();
						}
					}
				}
				else{
					System.out.println("Génération de clé "+msgCmd[1]+" failed");
					krakenThread.sendCommand(KrakenServerMsg.ERROR.makeCommand( " "+KrakenServerMsg.END_ENCRYPTION_DISTRIBUTION.msg));
				}
			}
			else if(msgCmd.length == 2){
				//serv local
				//1 = chemin fichier cle chiffree sur serv global
				
				ConfParser parser = new ConfParser();
				try {
					parser.parse(new File("/etc/kraken/local_conf/kraken.conf"));
				} catch (IOException e) {
					e.printStackTrace();
				}
				String dh_key;
				try {
					dh_key = CipherModule.readDHKey(parser.getValue("DH_KEY"));
					String newKeyFile = msgCmd[1].substring(0, msgCmd[1].length()-2);
					//attend la copie du fichier
					krakenThread.sendCommand(KrakenServerMsg.WAITING_FILE.makeCommand(" "+msgCmd[1], " "+msgCmd[1]));
					FileUtils.tcpWaitingFile(krakenThread.getSocket(),msgCmd[1]);
					
					//une fois le fichier copie, on le dechiffre et supprime le chiffre
					try {
						FileUtils.copyFileBufferedEncryptedDH(msgCmd[1], newKeyFile, 4, dh_key);
						new File(msgCmd[1]).delete();
						this.savedMessage = KrakenClientMsg.COPY_FILE.makeCommand();
						
						//fin d'attente du client
						krakenThread.sendCommand(KrakenServerMsg.END_WAITING_FILE.makeCommand());
					} catch (FileNotFoundException e) {
						e.printStackTrace();
					} catch (IOException e) {
						e.printStackTrace();
					}
					
				} catch (IOException e1) {
					e1.printStackTrace();
				}
			}
		}
		
		//fin de la communication
		else if(msgCmd[0].equalsIgnoreCase(KrakenClientMsg.END_CLIENT.toString())){
			krakenThread.sendCommand(KrakenServerMsg.END_SERVER.makeCommand());
			krakenThread.setRunOff(); //obligatoire pour arrêter le thread
		}
		
		// protocole diffie-hellman
		else if(msgCmd[0].equalsIgnoreCase(KrakenClientMsg.DH_KEY.toString())) {
			// serveur web
			if(msgCmd.length == 4) {
				// créer un nouveau client pour propager une nouvelle clé dh
				ArrayList<String> msgToGlobal = new ArrayList<String>();
				msgToGlobal.add(KrakenClientMsg.KRAKEN_CLIENT.toString());
				msgToGlobal.add(KrakenClientMsg.DH_KEY.makeCommand(" "+msgCmd[2], " "+msgCmd[3]));
				msgToGlobal.add(KrakenClientMsg.END_CLIENT.toString());
				
				KrakenClient krakenClient = new KrakenClient(msgToGlobal, msgCmd[1]);
				krakenClient.connect();
				krakenClient.communicate();
				
				String answer = krakenClient.getFinalAnswer();
				
				if(answer.equalsIgnoreCase("END_SERVER")) {
					System.out.println("Transfert TCP réussi");
					krakenThread.sendCommand(KrakenServerMsg.DH_ACK.makeCommand());
				}
				else
					krakenThread.sendCommand(KrakenServerMsg.ERROR.makeCommand(" TCP_TRANSFERT_FAILED"));
			}
			// serveur global
			else if(msgCmd.length == 3) {
				// generer PGA
				if(dh == null) {
					System.out.println("génération de P et G");
					dh = new DiffieHellman(4, 1024);
					try {
						dh.gen_P();
						dh.gen_G();
					} catch (InterruptedException e) {
						e.printStackTrace();
						krakenThread.sendCommand(KrakenServerMsg.ERROR.makeCommand(" DH_COULD_NOT_CREATE_P_OR_G"));
					}
					System.out.println("P et G générés");
				}
				
				try {
					dh.gen_A();
				} catch (InterruptedException e) {
					e.printStackTrace();
					krakenThread.sendCommand(KrakenServerMsg.ERROR.makeCommand(" DH_COULD_NOT_CREATE_A"));
				}
				
				ArrayList<String> msgToLocal = new ArrayList<String>();
				msgToLocal.add(KrakenClientMsg.KRAKEN_CLIENT.makeCommand());
				msgToLocal.add(KrakenClientMsg.DH_KEY.makeCommand());
				msgToLocal.add(KrakenClientMsg.SEND_P.makeCommand(" "+dh.getP().toString()));
				msgToLocal.add(KrakenClientMsg.SEND_G.makeCommand(" "+dh.getG().toString()));
				msgToLocal.add(KrakenClientMsg.SEND_A.makeCommand(" "+dh.getA().toString(), " "+msgCmd[2]));
				msgToLocal.add(KrakenClientMsg.END_CLIENT.makeCommand());
				
				KrakenClient krakenClient = new KrakenClient(msgToLocal, msgCmd[1]);
				krakenClient.connect();
				krakenClient.communicate();
				
				String answer = krakenClient.getFinalAnswer();
				
				if(answer.equalsIgnoreCase("END_SERVER"))
					System.out.println("Transfert TCP réussi");
				else
					krakenThread.sendCommand(KrakenServerMsg.ERROR.makeCommand(" TCP_TRANSFERT_FAILED"));
				
				// récupérer B
				dh.setB(krakenClient.getB());
				
				// générer la clé
				System.out.println("clé serveur global "+dh.compute_key(dh.getB()));
				
				// réponse ajoutée
				krakenThread.sendCommand(KrakenServerMsg.DH_ACK.makeCommand());
				
				try {
					ConfParser conf = new ConfParser();
					conf.parse(new File("/etc/kraken/global_conf/kraken.conf"));
					CipherModule.writeDHKey(conf.getValue("DH_KEY")+msgCmd[2]+".dh", dh.getK().toString());
				} catch (IOException e) {
					e.printStackTrace();
					krakenThread.sendCommand(KrakenServerMsg.ERROR.makeCommand(" CONF_CANT_OPEN_FILE"));
				}
			}
			
			// côté serveur local
			else if(msgCmd.length == 1) {
				dh = new DiffieHellman(4, 1024);
				krakenThread.sendCommand(KrakenServerMsg.DH_ACK.makeCommand());
			}
			
			// problème
			else
				krakenThread.sendCommand(KrakenServerMsg.UNKNOWN_COMMAND.makeCommand(msg));
		}
		
		// côté serveur local
		else if(msgCmd[0].equalsIgnoreCase(KrakenClientMsg.SEND_P.toString())) {
			// stocker P (synchroniser?)
			dh.setP(new BigInteger(msgCmd[1]));
			krakenThread.sendCommand(KrakenServerMsg.RECEIVED_P.makeCommand());
		}
		else if(msgCmd[0].equalsIgnoreCase(KrakenClientMsg.SEND_G.toString())) {
			// stocker G (synchroniser?)
			dh.setG(new BigInteger(msgCmd[1]));
			krakenThread.sendCommand(KrakenServerMsg.RECEIVED_G.makeCommand());
		}
		else if(msgCmd[0].equalsIgnoreCase(KrakenClientMsg.SEND_A.toString())) {
			if(msgCmd.length == 3) {
				// stocker A (synchroniser?)
				dh.setA(new BigInteger(msgCmd[1]));
				
				// générer B
				try {
					dh.gen_B();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
	
				krakenThread.sendCommand(KrakenServerMsg.SEND_B.makeCommand(" "+dh.getB().toString()));
				
				// générer la clé
				System.out.println("clé serveur local "+dh.compute_key(dh.getA()));
				try {
					ConfParser conf = new ConfParser();
					conf.parse(new File("/etc/kraken/local_conf/kraken.conf"));
					CipherModule.writeDHKey(conf.getValue("DH_KEY")+msgCmd[2]+".dh", dh.getK().toString());
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			else
				krakenThread.sendCommand(KrakenServerMsg.UNKNOWN_COMMAND.makeCommand(msg));
		}
		
		//Commande inconnue
		else
			krakenThread.sendCommand(KrakenServerMsg.UNKNOWN_COMMAND.makeCommand(msg));
			
	}
	
}
